package thread.n8;

import java.util.concurrent.locks.StampedLock;

/**
 * 读写锁的使用：StampedLock
 *      1：不可以重入。
 *      2：不支持条件变量。
 *      3：支持【乐观读】，这个是不加锁的，需要配合验证【戳】方法一起使用。
 *          先【tryOptimisticRead（获取一个乐观锁）】、之后【validate（验证数据是否被修改）】
 *      4：支持【读读共享（无论是乐观读锁，还是普通读锁）】。
 *      5：写锁会与其他所有的锁互斥。
 *      6：【tryOptimisticRead】、【readLock】、【writeLock】三种操作锁的方法，
 *          都会返回一个【stamp】，StampedLock就是通过这个【戳】，来控制【加锁/解锁】的，这个作用类似与一个【验证码】。
 *      7：【普通读锁，会阻塞写锁】、【乐观读锁，不会阻塞写锁，但是写锁，会导致乐观读锁失效】。
 */
public class StampedLockTest {

    /**
     * StampLock的加减锁，必须使用【戳】，也就是【加锁的返回值】。
     * 并且不能被重入。
     * @param args
     */
    public static void main1(String[] args) {
        StampedLock lock = new StampedLock();
        //加读锁。
        long stamp = lock.readLock();
        //释放读锁。
        lock.unlockRead(stamp);
        //加写锁。
        long stamp1 = lock.writeLock();
        //释放写锁。
        lock.unlockWrite(stamp1);
    }

    /**
     * 乐观读-锁升级：
     * 乐观读：StampedLock支持【tryOptimisticRead】获取一个乐观读锁。
     * 【读取完毕之后需要做一次 【戳校验】】，如果校验通过，表示这期间没有写操作，数据可以安全使用。
     * 如果校验，不通过，则需要重新获取读锁。保证数据安全。
     */
    private void test1(){
        StampedLock lock = new StampedLock();
        //返回一个【戳】。
        long stamp = lock.tryOptimisticRead();
        //校验【戳】，这里就是验证不通过。
        if(!lock.validate(stamp)){
            //锁升级。
        }
    }

    /**
     * 测试【乐观锁的读读】，不加锁。都是采用的【读乐观锁】的方式。
     * @param args
     */
    public static void main2(String[] args) throws InterruptedException {
        DataContainerStamped dataContainer = new DataContainerStamped(1);
        new Thread(() -> {
            try {
                dataContainer.read(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1").start();

        Thread.sleep(500);
        new Thread(() -> {
            try {
                dataContainer.read(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2").start();
    }


    /**
     * 测试【普通锁的读读】，不加锁。都是采用的的方式。
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
        StampedLock lock = new StampedLock();
        new Thread(() -> {
            //加一个普通的读锁。
            long stamp = lock.readLock();
            System.out.println("获取到一个普通的读锁。。。");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //解锁。。
            lock.unlockRead(stamp);
        }, "t1").start();

        new Thread(() -> {
            //加一个普通的读锁。
            long stamp = lock.readLock();
            System.out.println("获取到一个普通的读锁。。。");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //解锁。。
            lock.unlockRead(stamp);
        }, "t2").start();
    }


    /**
     * 测试：读写：这个时候，出现了【乐观读锁】，升级为：读锁的情况。
     * @param args
     */
    public static void main3(String[] args) throws InterruptedException {
        DataContainerStamped dataContainer = new DataContainerStamped(1);

        new Thread(() -> {
            try {
                dataContainer.read(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1").start();

        Thread.sleep(500);
        new Thread(() -> {
            dataContainer.write(100);
        }, "t2").start();
    }

    /**
     * 测试：先写，会阻塞后续的读和写操作。
     */
    public static void main5(String[] args) throws InterruptedException {
        DataContainerStamped dataContainer = new DataContainerStamped(1);

        new Thread(() -> {
            dataContainer.write(100);
        }, "t2").start();

        Thread.sleep(500);

        new Thread(() -> {
            try {
                dataContainer.read(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1").start();


    }
}


/**
 * 使用StampedLock实现的读写容器锁。
 */
class DataContainerStamped {

    private int data;
    private final StampedLock lock = new StampedLock();

    public DataContainerStamped(int data) {
        this.data = data;
    }

    //使用StampLock的读锁实现共享读。
    public int read(int readTime) throws InterruptedException {
        //先获取乐观读锁。
        long stamp = lock.tryOptimisticRead();
        System.out.println("optimistic read locking..."+stamp);
        Thread.sleep(readTime*1000);
        //判断【戳】是否有效，如果有效（意味着没有别的线程对其进行了修改），则直接返回。
        if (lock.validate(stamp)) {
            System.out.println("read finish..."+stamp+", data:"+data);
            return data;
        }
        // 锁升级 - 读锁
        System.out.println("updating to read lock..."+stamp);
        try {
            //获取一个读锁。
            stamp = lock.readLock();
            System.out.println("read lock "+stamp);
            Thread.sleep(readTime*1000);
            System.out.println("read finish..."+stamp+", data:"+data);
            return data;
        } finally {
            System.out.println("read unlock"+stamp);
            //释放读锁。
            lock.unlockRead(stamp);
        }
    }

    public void write(int newData) {
        long stamp = lock.writeLock();
        System.out.println("write lock"+stamp);
        try {
            Thread.sleep(2000);
            this.data = newData;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("write unlock {}"+stamp);
            lock.unlockWrite(stamp);
        }
    }

}
